import atexit
import cv2
import os
import pickle
import traceback
import PySimpleGUI as sg
from easydict import EasyDict
from pathlib import Path
from pdb import set_trace as b

sg.change_look_and_feel('Default')

class Utils:
    @staticmethod
    def is_video_filename(filename):
        try: return str(filename)[-4:] in ('.avi', '.mp4')
        except: return False

class Session:
    "maintain the status of the labeling progress"

    LaneBehaviors = ["ALONG", "TURN", "MERGE", "BRANCH", "HIGHWAY_ENTER", "HIGHWAY_EXIT"]
    LineTypes = ["BLOCKED", "TRANSITABLE", "REVERSIBLE"]

    class Lane(EasyDict):
        def __init__(self):
            super().__init__({
                "left" : None,
                "right" : None,
                "behavior" : None,
                "direction" : None,
                })

    class Line(EasyDict):
        def __init__(self):
            super().__init__({
                "points" : [],
                "type" : None
                })

    class Frame(EasyDict):
        def __init__(self):
            super().__init__({
                "lanes" : [],
                })

    class VideoClip(EasyDict):
        def __init__(self):
            super().__init__()

    def __init__(self, values):
        # immutable preferences
        self.root_str = values['folder_change']
        self.root_path = Path(self.root_str)
        #self.frame_step = values['frame_step']
        #self.frame_offset = values['frame_offset']
        self.row_range = range(int(values['row_start']), int(values['row_end']), int(values['row_step']))
        # save and load logic
        self.updated = True
        self.sess_save_path = None
        # data and view states
        self._data = EasyDict()
        self._view = EasyDict()
    
    def save(self, view, sess_save_path=None):
        if self.sess_save_path == sess_save_path and self.updated == False: return
        if sess_save_path is None: sess_save_path = self.sess_save_path
        if sess_save_path is None: return
        self._view['file_tree_selection'] = view.window['file_tree'].TKTreeview.focus()
        self._view['raw_frame_width'] = view.raw_frame_width
        self._view['raw_frame_height'] = view.raw_frame_height
        with open(sess_save_path, 'wb+') as f:
            pickle.dump(self, f)
        self.sess_save_path = sess_save_path
        self.updated = False

    @classmethod
    def from_file(cls, sess_save_path):
        with open(sess_save_path, 'rb') as f:
            self = pickle.load(f)
        self.sess_save_path = sess_save_path
        self.updated = False
        return self

##############################################################################################################################################################################################################################################################
##############################################################################################################################################################################################################################################################
##############################################################################################################################################################################################################################################################

class View:
    "interface to the labeler"
    __instance = None
    class MainMenuDef:
        __V = {
            '&Save' : '!&Save',
            '&Save As' : '!&Save As',
        }
        @classmethod
        def new(cls, make_enable=[], make_disable=[]):
            for key in cls.__V:
                if key in make_enable:
                    cls.__V[key] = key
                elif key in make_disable:
                    cls.__V[key] = '!' + key
            return [
                ['&Session', ['&New', '&Open', cls.__V['&Save'], cls.__V['&Save As'], '---', 'Properties', 'E&xit']],
                ['&Help', '&About...'],]
    class FileTree:
        DATA = sg.TreeData()
        LAYOUT = [sg.Tree(data=DATA, headings=['status'], auto_size_columns=True, num_rows=20, col0_width=18, key='file_tree', show_expanded=False, enable_events=True)]
        FOLDER_ICON = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAABnUlEQVQ4y8WSv2rUQRSFv7vZgJFFsQg2EkWb4AvEJ8hqKVilSmFn3iNvIAp21oIW9haihBRKiqwElMVsIJjNrprsOr/5dyzml3UhEQIWHhjmcpn7zblw4B9lJ8Xag9mlmQb3AJzX3tOX8Tngzg349q7t5xcfzpKGhOFHnjx+9qLTzW8wsmFTL2Gzk7Y2O/k9kCbtwUZbV+Zvo8Md3PALrjoiqsKSR9ljpAJpwOsNtlfXfRvoNU8Arr/NsVo0ry5z4dZN5hoGqEzYDChBOoKwS/vSq0XW3y5NAI/uN1cvLqzQur4MCpBGEEd1PQDfQ74HYR+LfeQOAOYAmgAmbly+dgfid5CHPIKqC74L8RDyGPIYy7+QQjFWa7ICsQ8SpB/IfcJSDVMAJUwJkYDMNOEPIBxA/gnuMyYPijXAI3lMse7FGnIKsIuqrxgRSeXOoYZUCI8pIKW/OHA7kD2YYcpAKgM5ABXk4qSsdJaDOMCsgTIYAlL5TQFTyUIZDmev0N/bnwqnylEBQS45UKnHx/lUlFvA3fo+jwR8ALb47/oNma38cuqiJ9AAAAAASUVORK5CYII='
        FILE_ICON = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAABU0lEQVQ4y52TzStEURiHn/ecc6XG54JSdlMkNhYWsiILS0lsJaUsLW2Mv8CfIDtr2VtbY4GUEvmIZnKbZsY977Uwt2HcyW1+dTZvt6fn9557BGB+aaNQKBR2ifkbgWR+cX13ubO1svz++niVTA1ArDHDg91UahHFsMxbKWycYsjze4muTsP64vT43v7hSf/A0FgdjQPQWAmco68nB+T+SFSqNUQgcIbN1bn8Z3RwvL22MAvcu8TACFgrpMVZ4aUYcn77BMDkxGgemAGOHIBXxRjBWZMKoCPA2h6qEUSRR2MF6GxUUMUaIUgBCNTnAcm3H2G5YQfgvccYIXAtDH7FoKq/AaqKlbrBj2trFVXfBPAea4SOIIsBeN9kkCwxsNkAqRWy7+B7Z00G3xVc2wZeMSI4S7sVYkSk5Z/4PyBWROqvox3A28PN2cjUwinQC9QyckKALxj4kv2auK0xAAAAAElFTkSuQmCC'
        @classmethod
        def add_files_in_folder(cls, parent, dirname):
            files = sorted(os.listdir(dirname))
            for f in files:
                fullname = os.path.join(dirname,f)
                if os.path.isdir(fullname):            # if it's a folder, add folder and recurse
                    cls.DATA.Insert(parent, fullname, f, values=[], icon=cls.FOLDER_ICON)
                    cls.add_files_in_folder(fullname, fullname)
                elif Utils.is_video_filename(fullname):
                    cls.DATA.Insert(parent, fullname, f, values=[], icon=cls.FILE_ICON)
    class DrawTools:
        LAYOUT = [sg.Frame('Draw', [
            [sg.Button('edge pencil', size=(15, 2)), sg.Button('binary pencil', size=(18, 2))],
            [sg.Button('blur', size=(10,2)), sg.Slider(range=(None, None), default_value=None, resolution=None, orientation='h', size=(15, 16))],
            [sg.Button('move', size=(10,2)), sg.Slider(range=(None, None), default_value=None, resolution=None, orientation='h', size=(15, 16))],
        ])]
    class VideoCanvas:
        CANVAS_SIZES = [(800, 480), (800, 480)]
        CANVAS_MAIN_IMAGE_WIDTHS = [800, 400]
        CANVAS_MAIN_IMAGE_HEIGHTS = [None, None]
        CANVAS_SIZE_MODE = 0
        SCALE = 1.
        LAYOUT = [
            [sg.TabGroup([[
                sg.Tab('Main', [
                    [sg.Graph(CANVAS_SIZES[0], graph_bottom_left=(0, 100), graph_top_right=(100, 0), key='canvas')],
                ]),
                sg.Tab('Segmentation', [
                    #[sg.Image(filename='', size=(640, 480), key='image_segmentation')],
                ]),
                ]])],
            [sg.Slider(range=(None, None), default_value=None, resolution=None, orientation='h', size=(35, 12), enable_events=True, key='canvas_slider'),
                sg.Button('<<', size=(1,1), key='canvas_back2'), sg.Button('<', size=(1,1), key='canvas_back'),
                sg.Spin([0], initial_value=0, key='canvas_frame_id', enable_events=True), sg.T('/0000', key='canvas_frame_count'),
                sg.Button('>', size=(1,1), key='canvas_forward'), sg.Button('>>', size=(1,1), key='canvas_forward2'),
                sg.Button('TOGGLE AREA', key='canvas_toggle_size_mode'),
                sg.Button('     √     ', key='canvas_mark'),
                ],
        ]
        VIDEOCAP = cv2.VideoCapture()
        VIDEO_FRAME_COUNT = None
        CUR_FRAME = None
        IMGBYTES = None
        CANVAS = None
        IMGID = None
        @classmethod
        def update_frame_size(cls, view, height, width, mode_id):
            assert mode_id in (0, 1)
            if cls.CANVAS is None: cls.CANVAS = view.window['canvas']
            canvas_node = cls.CANVAS
            cls.SCALE = cls.CANVAS_MAIN_IMAGE_WIDTHS[mode_id] / width
            cls.CANVAS_MAIN_IMAGE_HEIGHTS[mode_id] = int(height * cls.SCALE)
            left_margin = (cls.CANVAS_SIZES[mode_id][0] - cls.CANVAS_MAIN_IMAGE_WIDTHS[mode_id]) / cls.SCALE / 2
            right_margin = left_margin
            bottom_margin = (cls.CANVAS_SIZES[mode_id][1] - cls.CANVAS_MAIN_IMAGE_HEIGHTS[mode_id]) / cls.SCALE
            canvas_node.BottomLeft = (-left_margin, height+bottom_margin)
            canvas_node.TopRight = (width+right_margin, 0)
            cls.CANVAS_SIZE_MODE = mode_id
        @classmethod
        def open(cls, view, filename):
            if cls.VIDEOCAP.isOpened(): cls.VIDEOCAP.release()
            cls.VIDEOCAP.open(filename)
            if not cls.VIDEOCAP.isOpened(): return False
            cls.VIDEO_FRAME_COUNT = int(cls.VIDEOCAP.get(cv2.CAP_PROP_FRAME_COUNT))
            #cls.VIDEO_FRAME_COUNT = (cls.VIDEO_FRAME_COUNT - view.sess.frame_offset) // view.sess.frame_step
            view.window['canvas_frame_count'].update('/{}'.format(cls.VIDEO_FRAME_COUNT))
            view.window['canvas_frame_id'].update(value=1, values=list(range(1, cls.VIDEO_FRAME_COUNT+1)))
            view.window['canvas_slider'].update(value=1, range=(1, cls.VIDEO_FRAME_COUNT))
            cls.switch_to_frame(view, 1)
            return True
        @classmethod
        def switch_to_frame(cls, view, frame_id):
            if frame_id < 1: frame_id = 1
            elif frame_id > cls.VIDEO_FRAME_COUNT: frame_id = cls.VIDEO_FRAME_COUNT
            #cls.VIDEOCAP.set(cv2.CAP_PROP_POS_FRAMES, (frame_id - 1) * view.sess.frame_step + view.sess.frame_offset)
            cls.VIDEOCAP.set(cv2.CAP_PROP_POS_FRAMES, frame_id - 1)
            ret, raw_frame = cls.VIDEOCAP.read()
            if not ret: return
            cls.CUR_FRAME = cv2.resize(raw_frame, (cls.CANVAS_MAIN_IMAGE_WIDTHS[cls.CANVAS_SIZE_MODE], cls.CANVAS_MAIN_IMAGE_HEIGHTS[cls.CANVAS_SIZE_MODE]))
            cls.IMGBYTES = cv2.imencode('.png', cls.CUR_FRAME)[1].tobytes()
            if cls.CANVAS is None: cls.CANVAS = view.window['canvas']
            if cls.IMGID: cls.CANVAS.delete_figure(cls.IMGID)             # delete previous image
            cls.IMGID = cls.CANVAS.draw_image(data=cls.IMGBYTES, location=(0,0))    # draw new image
            cls.CANVAS.TKCanvas.tag_lower(cls.IMGID)           # move image to the "bottom" of all other drawings
            
    class CurrentFrameTreeView:
        DATA = sg.TreeData()
        LAYOUT = [
            sg.Frame('Current Frame:', [
                [sg.Tree(data=DATA, headings=['status'], auto_size_columns=True, num_rows=8, col0_width=23, key='cur_frame', show_expanded=False,)],
                [sg.Button('pair', size=(20,1)), sg.Button('unpair', size=(20,1))],
            ]),
        ]
    class LanePropertiesView:
        TAB1 = [
            [
                sg.Frame('BEHAVIOR TYPE:', [
                    [sg.Radio('ALONG', "BEHAVIOR_TYPE"), sg.Radio('TURN', "BEHAVIOR_TYPE"), sg.Radio('MERGE', "BEHAVIOR_TYPE"), sg.Radio('BRANCH', "BEHAVIOR_TYPE"),],
                    [sg.Radio('HW_ENTER', "BEHAVIOR_TYPE"), sg.Radio('HW_EXIT', "BEHAVIOR_TYPE"), sg.Radio('SIDE_LANE', "BEHAVIOR_TYPE"),],
                ])
            ],
            [
                sg.Column([[sg.Frame('LEFT LINE:', [
                    [sg.Radio('PASSABLE', "LEFT_LINE_PASSABILITY"),],
                    [sg.Radio('BLOCKED', "LEFT_LINE_PASSABILITY"),],
                    [sg.Radio('TEMP_BLOCKED', "LEFT_LINE_PASSABILITY"),],
                    [sg.Radio('REVERSIBLE', "LEFT_LINE_PASSABILITY"),],
                    [sg.Radio('TRANS_P2B', "LEFT_LINE_PASSABILITY"),],
                    [sg.Radio('TRANS_B2P', "LEFT_LINE_PASSABILITY"),],
                    [sg.Radio('CURB', "LEFT_LINE_PASSABILITY"),],
                    [sg.Button('select point', size=(5, 1)), sg.T('(200, 44)')],
                ])]]),
                sg.Column([[sg.Frame('RIGHT LINE:', [
                    [sg.Radio('PASSABLE', "RIGHT_LINE_PASSABILITY"),],
                    [sg.Radio('BLOCKED', "RIGHT_LINE_PASSABILITY"),],
                    [sg.Radio('TEMP_BLOCKED', "RIGHT_LINE_PASSABILITY"),],
                    [sg.Radio('REVERSIBLE', "RIGHT_LINE_PASSABILITY"),],
                    [sg.Radio('TRANS_P2B', "RIGHT_LINE_PASSABILITY"),],
                    [sg.Radio('TRANS_B2P', "RIGHT_LINE_PASSABILITY"),],
                    [sg.Radio('CURB', "RIGHT_LINE_PASSABILITY"),],
                    [sg.Button('select point', size=(5, 1)), sg.T('(200, 44)')],
                ])]]),
            ],
        ]
        TAB2 = [
            [sg.Frame('STOP LINE:', [
                [sg.Button('select point'), sg.T('(200, 44)')],
                [sg.Button('select lamp'), sg.T('(200, 44, 50, 50)')],
                [sg.Radio('GREEN', "TRAFFIC_LAMP"), sg.Radio('YELLOW', "TRAFFIC_LAMP"),sg.Radio('RED', "TRAFFIC_LAMP"), ],
                [sg.Radio('STOP_SIGN', "TRAFFIC_LAMP"),sg.Radio('NOTHING', "TRAFFIC_LAMP"),],
            ])],
        ]
        LAYOUT = [sg.TabGroup([[sg.Tab('Basic', TAB1), sg.Tab('StopLine', TAB2)]])]
    def __init__(self):
        if View.__instance is not None: raise "singleton"
        View.__instance = self
        # layouts
        self.left_column = sg.Column([
            self.FileTree.LAYOUT,
            self.DrawTools.LAYOUT,
        ])
        self.main_column = sg.Column(self.VideoCanvas.LAYOUT)
        self.right_column = sg.Column([
            self.CurrentFrameTreeView.LAYOUT,
            self.LanePropertiesView.LAYOUT,
        ])
        self.layout = [
            [sg.Menu(self.MainMenuDef.new(), key='menu')],
            [self.left_column, self.main_column, self.right_column]
        ]
        # widgets
        self.window = sg.Window('Window Title', self.layout,
            font=("Helvetica", 8),
            margins=(0, 0),
            return_keyboard_events=True,
            )
        # states
        self.ok = True # break trigger for outter loop
        self.debug = 2 ################ MAIN_DEBUG_FLAG
        self.raw_frame_width = None
        self.raw_frame_height = None
        atexit.register(self.cleanup)
    def cleanup(self):
        self.window.close()
    def spinOnce(self, timeout=None):
        event, values = self.window.read(timeout=timeout)
        if self.debug == 1: ##################
            event = 'New' ##################
            self.debug = 0 ##################
        elif self.debug == 2: ##################
            event = 'Open' ##################
            self.debug = 3 ##################
        elif self.debug == 3: ##################
            self.debug = 0
        else:pass ####################
        if event is None: self.ok = False; return event
        if event is sg.TIMEOUT_KEY: return event
        event = 'event__' + ''.join(e for e in event if e.isalnum() or e == '_')
        try: getattr(self, event)(values)
        except Exception as e:
            print("unhandled event \"{}\" or exception \"{}\"".format(event, e))
            traceback.print_exc()
        return event
    def event__New(self, values):
        LEFT_COL_WIDTH = 14
        dialog_layout = [
            [sg.T('Open Folders', size=(LEFT_COL_WIDTH, 1)), sg.In(size=(34, 1), enable_events=True, key='folder_change'), sg.FolderBrowse(key='folder')],
            #[sg.T('Frame Step',   size=(LEFT_COL_WIDTH, 1)), sg.Spin(list(range(1,1000)), initial_value=60, key='frame_step', enable_events=True), 
            #    sg.T('', size=(5,1)), sg.T('Frame Offset', size=(LEFT_COL_WIDTH, 1)), sg.Spin(list(range(0, 60)), initial_value=0, key='frame_offset', enable_events=True)],
            #[sg.T('', size=(LEFT_COL_WIDTH, 1)), sg.T('', key='frame_step_memo', size=(35,1))],
            [sg.T('Sample Rows',  size=(LEFT_COL_WIDTH, 1)),
                sg.T('start'), sg.Spin(list(range(1,10000)), initial_value=  0, key='row_start', size=(6,1), enable_events=True),
                sg.T('step'),  sg.Spin(list(range(1,10000)), initial_value= 10, key='row_step',  size=(6,1), enable_events=True),
                sg.T('end'),   sg.Spin(list(range(1,10000)), initial_value=480, key='row_end',   size=(6,1), enable_events=True)],
            [sg.T('Preview',      size=(LEFT_COL_WIDTH, 1)), sg.Graph((320, 240), graph_bottom_left=(0, 240), graph_top_right=(320, 0), background_color='grey', key='preview')],
            [sg.T(' ',            size=(25, 1)), sg.Button('change image', key='change_image'), sg.OK(disabled=True), sg.Cancel()], ]
        dialog = sg.Window('New Session', dialog_layout)
        preview_node = dialog['preview']
        imgbytes = None
        imheight, imwidth = None, None
        video_fps = None
        video_id = 0
        files = []
        def init_preview(values, raw_frame):
            nonlocal imheight, imwidth, imgbytes
            imheight, imwidth = raw_frame.shape[0], raw_frame.shape[1]
            preview_node.BottomLeft = (0, imheight)
            preview_node.TopRight = (imwidth, 0)
            dialog['row_start'].update(imheight//2)
            dialog['row_end'].update(imheight)
            values['row_start'] = imheight//2
            values['row_end'] = imheight
            imgbytes = cv2.imencode('.png', cv2.resize(raw_frame, (320, 240)))[1].tobytes()
        def redraw_preview(values, raw_frame=None):
            nonlocal imheight, imwidth, imgbytes
            if raw_frame: assert imheight == raw_frame.shape[0] and imwidth == raw_frame.shape[1], "All videos should have identity frame size"
            if imgbytes is None and raw_frame is None: return
            elif raw_frame is not None: imgbytes = cv2.imencode('.png', cv2.resize(raw_frame, (320, 240)))[1].tobytes()
            preview_node.erase()
            preview_node.draw_image(data=imgbytes, location=(0,0))
            for row in range(int(values['row_start']), int(values['row_end']), int(values['row_step'])):
                preview_node.draw_line((0, row), (imwidth, row), color='green')
        def update_frame_step_memo(values):
            if video_fps is None: return
            dialog['frame_offset'].update(values=list(range(0, int(dialog['frame_step'].get()))))
            dialog['frame_step_memo'].update('(one frame per {:.2f} video seconds)'.format(values['frame_step']/video_fps))
        def get_first_frame(filename):
            nonlocal video_fps
            cap = cv2.VideoCapture(filename)
            assert cap.isOpened()
            video_fps = cap.get(cv2.CAP_PROP_FPS)
            _, raw_frame = cap.read()
            cap.release()
            return raw_frame
        debug = 1 ############### NEW_DEBUG_FLAG
        while True:
            event, values = dialog.read(timeout=100, timeout_key='timeout')
            if debug == 1:  ####################
                event = 'folder_change' ############
                dialog['folder_change'].update('/home/hyuyao/src/diml_data_labeler_lane/DIML/') ############
                values['folder'] = '/home/hyuyao/src/diml_data_labeler_lane/DIML/'  ##################
                debug = 2 ####################
            elif debug == 2:####################
                dialog['row_start'].update(140)
                debug = 3 ####################
            elif debug == 3:####################
                event = 'OK'####################
            else:pass ####################
            if event == 'OK':
                self.sess = Session(values)
                self.raw_frame_height = imheight
                self.raw_frame_width = imwidth
                # redraw menu
                self.window['menu'].update(self.MainMenuDef.new(
                    make_enable=['&Save', '&Save As']))
                # redraw main canvas
                self.VideoCanvas.update_frame_size(self, imheight, imwidth, 0)
                # redraw file tree
                self.FileTree.add_files_in_folder('', values['folder_change'])
                self.window['file_tree'].update(self.FileTree.DATA)
                self.filetree_tk = self.window['file_tree'].TKTreeview
                break
            elif event in ('Cancel', None): break
            elif event == 'folder_change':
                files = [ str(f) for f in Path(values['folder']).glob('**/*') if Utils.is_video_filename(f) ]
                if len(files) == 0:
                    sg.Popup("no video file found in path!", title='Warning')
                    dialog['OK'].update(disabled=True)
                    continue
                raw_frame = get_first_frame(files[video_id])
                #update_frame_step_memo(values)
                init_preview(values, raw_frame)
                redraw_preview(values)
                dialog['OK'].update(disabled=False)
            elif event in ('row_start', 'row_end', 'row_step'):
                redraw_preview(values)
            elif event == 'frame_step':
                update_frame_step_memo(values)
            elif event == 'frame_offset': pass 
            elif event == 'change_image':
                if len(files) == 0: continue
                video_id = (video_id + 1) % len(files)
                raw_frame = get_first_frame(files[video_id])
                redraw_preview(values, raw_frame)
            elif event == 'timeout': pass
            else: raise Exception('unknown event:{}'.format(event))
        dialog.close()
    def event__Open(self, values):
        if self.debug == 3: sess_save_path = '/home/hyuyao/src/diml_data_labeler_lane/demo.session'
        else: sess_save_path = sg.PopupGetFile('open a labeling session ...', 'Open Session', file_types=(('SESSION FILES', '*.session'), ('ALL Files', '*.*'),))
        self.sess = Session.from_file(sess_save_path)
        self.raw_frame_height = self.sess._view['raw_frame_height']
        self.raw_frame_width  = self.sess._view['raw_frame_width']
        # redraw menu
        self.window['menu'].update(self.MainMenuDef.new(
            make_enable=['&Save As'], make_disable=['&Save']))
        # redraw main canvas
        self.VideoCanvas.update_frame_size(self, self.raw_frame_height, self.raw_frame_width, 0)
        # redraw file tree
        self.FileTree.add_files_in_folder('', self.sess.root_str)
        self.window['file_tree'].update(self.FileTree.DATA)
        self.filetree_tk = self.window['file_tree'].TKTreeview
        file_tree_selection = self.sess._view['file_tree_selection'] if 'file_tree_selection' in self.sess._view else None
        if len(file_tree_selection):
            self.filetree_tk.selection_set(file_tree_selection)
            self.filetree_tk.see(file_tree_selection)
        # debug #######################
        if self.debug == 3:
            print('file_tree_selection:', file_tree_selection)
            print('raw_frame_height:', self.raw_frame_height)
            print('raw_frame_width:',  self.raw_frame_width)
    def event__Save(self, values):
        if self.sess.sess_save_path is None: self.event__SaveAs(values)
        else: self.sess.save(self)
    def event__SaveAs(self, values):
        sess_save_path = sg.PopupGetFile('save the labeling session to ...', 'Save Session', save_as=True, default_extension='.session', file_types=(('SESSION FILES', '*.session'), ('ALL Files', '*.*'),))
        self.sess.save(self, sess_save_path)
    def event__Exit(self, values):
        if not self.sess.updated: self.ok = False; return
        sg.Popup('Session Not Saved!')
    def event__file_tree(self, values):
        filename = values['file_tree'][0]
        if not Utils.is_video_filename(filename): return
        if not self.VideoCanvas.open(self, filename): return # TODO: open failed
    def event__canvas_frame_id(self, values):
        self.VideoCanvas.switch_to_frame(self, values['canvas_frame_id'])
    def event__canvas_forward(self, values):
        canvas_frame_id_node = self.window['canvas_frame_id']
        new_frame_id = int(canvas_frame_id_node.get()) + 5
        if new_frame_id < 1 or new_frame_id > self.VideoCanvas.VIDEO_FRAME_COUNT: return
        canvas_frame_id_node.update(value=new_frame_id)
        self.VideoCanvas.switch_to_frame(self, int(canvas_frame_id_node.get()))
    def event__canvas_forward2(self, values):
        canvas_frame_id_node = self.window['canvas_frame_id']
        new_frame_id = int(canvas_frame_id_node.get()) + 15
        if new_frame_id < 1 or new_frame_id > self.VideoCanvas.VIDEO_FRAME_COUNT: return
        canvas_frame_id_node.update(value=new_frame_id)
        self.VideoCanvas.switch_to_frame(self, int(canvas_frame_id_node.get()))
    def event__canvas_back(self, values):
        canvas_frame_id_node = self.window['canvas_frame_id']
        new_frame_id = int(canvas_frame_id_node.get()) - 5
        if new_frame_id < 1 or new_frame_id > self.VideoCanvas.VIDEO_FRAME_COUNT: return
        canvas_frame_id_node.update(value=new_frame_id)
        self.VideoCanvas.switch_to_frame(self, int(canvas_frame_id_node.get()))
    def event__canvas_back2(self, values):
        canvas_frame_id_node = self.window['canvas_frame_id']
        new_frame_id = int(canvas_frame_id_node.get()) - 15
        if new_frame_id < 1 or new_frame_id > self.VideoCanvas.VIDEO_FRAME_COUNT: return
        canvas_frame_id_node.update(value=new_frame_id)
        self.VideoCanvas.switch_to_frame(self, int(canvas_frame_id_node.get()))
    def event__canvas_toggle_size_mode(self, values):
        self.VideoCanvas.update_frame_size(self, self.raw_frame_height, self.raw_frame_width, 1-self.VideoCanvas.CANVAS_SIZE_MODE)
        canvas_frame_id_node = self.window['canvas_frame_id']
        self.VideoCanvas.switch_to_frame(self, int(canvas_frame_id_node.get()))
    def event__canvas_slider(self, values):
        canvas_frame_id_node = self.window['canvas_frame_id']
        canvas_frame_id_node.update(int(values['canvas_slider']))
        self.VideoCanvas.switch_to_frame(self, int(values['canvas_slider']))
        


if __name__ == '__main__':
    view = View()
    while view.ok:
        event = view.spinOnce(timeout=100)